#include "Vtop.h"
#include "verilated_vcd_c.h"
#include "common.h"
#include "pmem.h"
#include "reg.h"
#include "Vtop__Dpi.h"
#include "svdpi.h"
#include "state.h"
#include "sdb.h"
#include "ftrace.h"
#include "iringbuf.h"
#include "verilated_dpi.h"
uint64_t *perf_cnts = NULL;
extern "C" void set_cnt_ptr(const svOpenArrayHandle r) {
perf_cnts = (uint64_t *)(((VerilatedDpiOpenVar*)r)->datap());
}
static Vtop* top; 
static VerilatedContext* contextp = new VerilatedContext;
static VerilatedVcdC* tfp = new VerilatedVcdC;
static char log_buf[128];
void difftest_step(paddr_t pc,paddr_t npc);
void difftest_skip_ref();
void difftest_cp_intr();
void difftest_cp_csrs();
void difftest_cpyregs();
void device_update();
static void trace_and_difftest(void){
	svSetScope(svGetScopeFromName("TOP.top.cpu"));	
	svBit write_back = 0;
	WRITE_BACK(&write_back);
#ifdef CONFIG_WATCHPOINT
if(monitor_wp() == 1) npc_state.state = NPC_STOP;
#endif
#ifdef CONFIG_FTRACE
svBit is_jal_J;
svBit is_jalr_I;
svBitVecVal dest;
svBitVecVal rs1;
word_t imm_o;
word_t npc;
if(top->clock){
	svSetScope(svGetScopeFromName("TOP.top.cpu.idu"));	
	FTRACE_INFO(&is_jal_J,&is_jalr_I,&dest,&rs1,(svBitVecVal*)&imm_o);
	svSetScope(svGetScopeFromName("TOP.top.cpu"));
	GET_NPC((svBitVecVal*)&npc);
	if(is_jal_J == 1){ftrace_jal(get_pc(),dest,npc);}
	if(is_jalr_I == 1){ftrace_jalr(get_pc(),dest,rs1,imm_o,npc);}
}			
#endif
#if (defined CONFIG_TRACE)||(defined CONFIG_IRINGBUF)
word_t npc1;
char *p = log_buf;
if(top->clock){
	svSetScope(svGetScopeFromName("TOP.top.cpu"));
	GET_NPC((svBitVecVal*)&npc1);
	p += snprintf(p,sizeof(log_buf),"0x%016lx:",get_pc());
	int ilen = 4;
	int i;
	uint32_t inst = paddr_read(get_pc(),4);
	uint8_t *inst_b = (uint8_t *)&inst;
	for(i=ilen -1;i>=0;i--){
		p += snprintf(p,4," %02x",inst_b[i]);
	}
	int ilen_max = 4;
	int space_len = ilen_max - ilen;
	if(space_len <0) space_len =0;
	space_len = space_len * 3 + 1;
	memset(p,' ',space_len);
	p += space_len;
	void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte);
	char str1[10];
	uint8_t str2[10];
	disassemble(p,log_buf+sizeof(log_buf)-p,get_pc(),(uint8_t*)&inst,ilen);
#ifdef CONFIG_IRINGBUF
	if(write_back == 1){
	w_iringbuf(log_buf);
	}
#endif
#ifdef CONFIG_TRACE
	if(write_back == 1){
	Log("%s",log_buf);}
#endif
}
#endif 
#ifdef CONFIG_DIFFTEST
if(top->clock){
svSetScope(svGetScopeFromName("TOP.top.cpu"));
svBit is_clint = 0;
IS_CLINT(&is_clint);
if(is_clint){
	difftest_skip_ref();
	return;
}
if(write_back == 1){
word_t npc2;
GET_NPC((svBitVecVal*)&npc2);
	difftest_cp_csrs();
difftest_step(get_pc(),npc2);
	}
svBit is_intr = 0;
IS_INTR(&is_intr);
if(is_intr){
	difftest_cp_intr();	
	difftest_cpyregs();
	difftest_cp_csrs();
}
}
#endif
#ifdef CONFIG_PERF
	static uint64_t last_inst_cnt=0;
	if(perf_cnts[0] > last_inst_cnt + CONFIG_PERF_FREQUENCE)
	{
					last_inst_cnt = perf_cnts[0];
					printf("clk cnt = %lu\n",perf_cnts[0]);
					printf("inst_cnt = %lu\n",perf_cnts[1]);
					printf("CPI = %f\n",(float)perf_cnts[1]/(float)perf_cnts[0]);
	}
#endif
}

void init_verilator(void){
#ifdef CONFIG_WAVE
	contextp->traceEverOn(true);//open trace
	top->trace(tfp,0);
	tfp->open("wave.vcd");
#endif
}
void init_sim_tool(void){
	top = new Vtop;
	init_verilator();
	top->clock = 0;
	top->reset = 1;
	top->eval();
	top->clock = 1;
	top->eval();
}

void cpu_rst(void){
	top->clock = 1;
	top->reset = 1;
	top->eval();
	while(1){
		contextp->timeInc(1);
		if(contextp->time()<8){
						top->reset =1;
		}else top->reset =0;
		if(top->reset == 0)break;
		top->clock = !top->clock;
		top->eval();
#ifdef CONFIG_WAVE
		tfp->dump(contextp->time());
#endif
	}
}


void cpu_run(uint64_t n){
	assert(n>0);
	switch (npc_state.state){
		case NPC_END: case NPC_ABORT:
			printf("Program execution has ended. To restart the program, exit NPC and run again.\n");
			return;
		default: npc_state.state = NPC_RUNNING;
	}


	//execute(n)
	uint64_t cnt = 0;
	svBit soft_break;
	while(1){
		top->clock=!top->clock;
		if(!top->clock&&top->reset == 0){
			cnt++;
		}	
		top->eval();
#ifdef CONFIG_WAVE
//		if(get_pc()>=0x8303b24c && get_pc()<=0x8303b58c){
		tfp->dump(contextp->time());
//		}
#endif
#ifdef CONFIG_DEVICE
		device_update();
#endif
		trace_and_difftest();//do every cycle
		svSetScope(svGetScopeFromName("TOP.top.cpu.idu"));	
		EBREAK(&soft_break);
		if(soft_break!=0){npc_state.state = NPC_END;break;}
		contextp->timeInc(1);		
		if(cnt == n)break;
		if(npc_state.state != NPC_RUNNING) break;
	}



	switch(npc_state.state){
		case NPC_RUNNING:npc_state.state = NPC_STOP;break;
		case NPC_END:case NPC_ABORT:
			Log("npc:%s",(npc_state.state == NPC_ABORT? ANSI_FMT("ABORT",ANSI_FG_RED):
					(gpr(10) == 0? ANSI_FMT("HIT GOOD TRAP",ANSI_FG_GREEN):
					 ANSI_FMT("HIT BAD TRAP",ANSI_FG_RED))));
		default: break;
	}
}
void sdb_exit(){
	tfp->close();
	top->final();
}
